Function.RegisterNamespace("Test.System.Script.Strategy");

[Fixture]
Test.System.Script.Strategy.StrategyManager=function(){
    [Fixture]
    function StrategyManager(){
        [Fact]
        function ThrowsIfArgumentIsUndefined(){
            var expected=String.Format("System.Script.Strategy.StrategyManager.ctor: 'strategy' at position [{0}] does not implement IStrategySpecification.",0);
        
            var actual=Record.Exception(function(){
                new System.Script.Strategy.StrategyManager(undefined);
            });
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function ThrowsIfArgumentIsNull(){
            var expected=String.Format("System.Script.Strategy.StrategyManager.ctor: 'strategy' at position [{0}] does not implement IStrategySpecification.",0);
        
            var actual=Record.Exception(function(){
                new System.Script.Strategy.StrategyManager(null);
            });
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function ThrowsIfArgumentDoesNotImplementIStrategy(){
            var expected=String.Format("System.Script.Strategy.StrategyManager.ctor: 'strategy' at position [{0}] does not implement IStrategySpecification.",0);
        
            var actual=Record.Exception(function(){
                new System.Script.Strategy.StrategyManager(new Object());
            });
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function DoesNotThrowIfArgumentIsOmitted(){
            var actual=Record.Exception(function(){
                new System.Script.Strategy.StrategyManager();
            });
            
            Assert.Null(actual);
        }

        [Fact]
        function AddsStrategyPassedToConstructor(){
            var expected=function(){
                this.IsSatisfiedBy=function(candidate){};
            }
            
            var target=new System.Script.Strategy.StrategyManager(expected);
            var actual=target.Enumerate()[0];
            
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function AddsAllStrategiesPassedToConstructor(){
            var expected=[
                function(){
                    this.IsSatisfiedBy=function(candidate){};
                },
                function(){
                    this.IsSatisfiedBy=function(candidate){};
                },
                function(){
                    this.IsSatisfiedBy=function(candidate){};
                }
            ];                
            
            var target=new System.Script.Strategy.StrategyManager(expected[0],expected[1],expected[2]);
            var actual=target.Enumerate();
            
            Assert.Equal(expected,actual);
        }
    }
    
    [Fixture]
    function Add(){
        [Fact]
        function ThrowsIfStrategyIsOmitted(){
            var expected="System.Script.Strategy.StrategyManager.Add: 'strategy' must implement System.Script.Strategy.IStrategySpecification";
        
            var actual=Record.Exception(function(){
                new System.Script.Strategy.StrategyManager().Add();
            });
        
            Assert.Equal(expected,actual);
        }

        [Fact]
        function ThrowsIfStrategyIsUndefined(){
            var expected="System.Script.Strategy.StrategyManager.Add: 'strategy' must implement System.Script.Strategy.IStrategySpecification";
        
            var actual=Record.Exception(function(){
                new System.Script.Strategy.StrategyManager().Add(undefined);
            });
        
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function ThrowsIfStrategyIsNull(){
            var expected="System.Script.Strategy.StrategyManager.Add: 'strategy' must implement System.Script.Strategy.IStrategySpecification";
        
            var actual=Record.Exception(function(){
                new System.Script.Strategy.StrategyManager().Add(null);
            });
        
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function ThrowsIfStrategyDoesNotImplementIStrategySpecification(){
            var expected="System.Script.Strategy.StrategyManager.Add: 'strategy' must implement System.Script.Strategy.IStrategySpecification";
        
            var actual=Record.Exception(function(){
                new System.Script.Strategy.StrategyManager().Add(new Object());
            });
        
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function DoesNotThrowIfStrategyImplementsIStrategySpecification(){
            var actual=Record.Exception(function(){
                new System.Script.Strategy.StrategyManager().Add(function(){
                    this.IsSatisfiedBy=function(candidate){};
                });
            });
        
            Assert.Null(actual);
        }
        
        [Fact]
        function AddsStrategy(){
            var expected=function(){
                this.IsSatisfiedBy=function(candidate){};
            }
            var target=new System.Script.Strategy.StrategyManager();
            
            target.Add(expected);
            var actual=target.Enumerate()[0];
            
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function ClearsPreviouslyCachedStrategy(){
            var target=new System.Script.Strategy.StrategyManager();
            var targetCalls=0;
            var previousStrategy=function(){
                this.IsSatisfiedBy=function(candidate){
                    return ++targetCalls<2;
                };
            }
            var expected=function(){
                this.IsSatisfiedBy=function(candidate){
                    return true;
                };
            }
            target.Add(previousStrategy);
            target.Get(null);
            
            target.Add(expected);
            var actual=target.Get(null).constructor;
            
            Assert.Equal(expected,actual);
        }   
    }
    
    [Fixture]
    function Clear(){
        [Fact]
        function ClearsStrategies(){
            var target=new System.Script.Strategy.StrategyManager();
            target.Add(function(){
                this.IsSatisfiedBy=function(candidate){
                    return ++targetCalls<2;
                };
            });
            
            target.Clear();
            var actual=target.Enumerate().length;
            
            Assert.True(actual==0);
        }
        
        [Fact]
        function ClearsPreviouslyCachedStrategy(){
            var target=new System.Script.Strategy.StrategyManager();
            var targetStrategy=function(){
                this.IsSatisfiedBy=function(candidate){
                    return true;
                };
            }
            target.Add(targetStrategy);
            target.Get(null);
            var expected="System.Script.Strategy.StrategyManager.Get: No viable strategy found.";

            target.Clear();
            var actual=Record.Exception(function(){
                target.Get(null);
            });

            Assert.Equal(expected,actual);
        }   
    }
    
    [Fixture]
    function Get(){
        [Fact]
        function InstantiatesStrategy(){
            var targetStrategy=function(){
                actual=true;
                this.IsSatisfiedBy=function(candidate){
                    return true;
                }
            }
            var actual=false;
            
            new System.Script.Strategy.StrategyManager(targetStrategy).Get();
            
            Assert.True(actual);
        }
    
        [Fact]
        function ThrowsIfInstantiationThrows(){
            var targetStrategy=function targetStrategy(){
                throw new Error();
            }
            targetStrategy.IsSatisfiedBy=function(candidate){
                return true;
            };
        
            var actual=Record.Exception(function(){
                new System.Script.Strategy.StrategyManager(targetStrategy).Get();
            });

            Assert.NotNull(actual);
        }
        
        [Fact]
        function ThrowsWhatInstantiationThrows(){
            var targetError=new Error();
            var targetStrategy=function targetStrategy(){
                throw targetError;
            }
            targetStrategy.IsSatisfiedBy=function(candidate){
                return true;
            };
            var expected=String.Format("System.IO.File.getStrategy: unable to instantiate strategy '{0}'. Check your constructor logic for dependencies. Error: {1}",Function.GetName(targetStrategy),targetError);
        
            var actual=Record.Exception(function(){
                new System.Script.Strategy.StrategyManager(targetStrategy).Get();
            });

            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function CallsIsSatisfiedByMethod(){
            var expected=new Object();
            var targetStrategy=function(){
                this.IsSatisfiedBy=function(candidate){
                    actual=true;
                    return true;
                };
            }
            var actual=false;
            
            new System.Script.Strategy.StrategyManager(targetStrategy).Get();
            
            Assert.True(actual);
        }

        [Fact]
        function PassesCandidateToIsSatisfiedByMethod(){
            var expected=new Object();
            var targetStrategy=function(){
                this.IsSatisfiedBy=function(candidate){
                    actual=candidate;
                    return true;
                };
            }
            var actual=null;
            
            new System.Script.Strategy.StrategyManager(targetStrategy).Get(expected);
            
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function ReturnsStrategyIfIsSatisfiedByMethodReturnsTrue(){
            var expected=function(){
                this.IsSatisfiedBy=function(candidate){
                    return true;
                };
            }
            
            var actual=new System.Script.Strategy.StrategyManager(expected).Get().constructor;
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function ProcessesAllStrategiesIfIsSatisfiedByMethodReturnsFalse(){
            var targetStrategy=function(){
                this.IsSatisfiedBy=function(candidate){
                    actual++;
                    return false;
                };
            }
            var expected=3;
            var actual=0;
            
            Record.Exception(function(){
                new System.Script.Strategy.StrategyManager(targetStrategy,targetStrategy,targetStrategy).Get();
            });

            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function ThrowsIfNoStrategyIsFound(){
            var target=function(){
                this.IsSatisfiedBy=function(candidate){
                    return false;
                };
            }
            var expected="System.Script.Strategy.StrategyManager.Get: No viable strategy found.";
            
            var actual=Record.Exception(function(){
                new System.Script.Strategy.StrategyManager(target,target,target).Get();
            });

            Assert.Equal(expected,actual);
        }

        [Fact]
        function StopsProcessingStrategiesIfIsSatisfiedByMethodReturnsTrue(){
            var unexpectedStrategy=function(){
                this.IsSatisfiedBy=function(candidate){
                    throw new Error("Test.System.Script.");
                };
            }
            var targetStrategy=function(){
                this.IsSatisfiedBy=function(candidate){
                    return true;
                };
            }
            
            var actual=Record.Exception(function(){
                new System.Script.Strategy.StrategyManager(targetStrategy,unexpectedStrategy).Get();
            });
            
            Assert.Null(actual);
        }
    }
    
    [Fixture]
    function Enumerate(){
        [Fact]
        function ReturnsEmptyArrayIfNoStrategiesAreRegistered(){
            var expected=[];
            
            var actual=new System.Script.Strategy.StrategyManager().Enumerate();
            
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function ReturnsArrayOfStrategies(){
            var targetStrategy=function(){
                this.IsSatisfiedBy=function(candidate){
                    return true;
                };
            }
            var expected=[targetStrategy,targetStrategy,targetStrategy];
            
            var actual=new System.Script.Strategy.StrategyManager(targetStrategy,targetStrategy,targetStrategy).Enumerate();
            
            Assert.Equal(expected,actual);
        }
    }

    [Fixture]
    function Remove(){
        [Fact]
        function ThrowsIfStrategyIsOmitted(){
            var expected="System.Script.Strategy.StrategyManager.Remove: 'strategy' must implement System.Script.Strategy.IStrategySpecification";

            var actual=Record.Exception(function(){
                new System.Script.Strategy.StrategyManager().Remove();
            });
            
            Assert.Equal(expected,actual);
        }

        [Fact]
        function ThrowsIfStrategyIsUndefined(){
            var expected="System.Script.Strategy.StrategyManager.Remove: 'strategy' must implement System.Script.Strategy.IStrategySpecification";

            var actual=Record.Exception(function(){
                new System.Script.Strategy.StrategyManager().Remove(undefined);
            });
            
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function ThrowsIfStrategyIsNull(){
            var expected="System.Script.Strategy.StrategyManager.Remove: 'strategy' must implement System.Script.Strategy.IStrategySpecification";

            var actual=Record.Exception(function(){
                new System.Script.Strategy.StrategyManager().Remove(null);
            });
            
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function ThrowsIfStrategyDoesNotImplementIStrategySpecification(){
            var expected="System.Script.Strategy.StrategyManager.Remove: 'strategy' must implement System.Script.Strategy.IStrategySpecification";

            var actual=Record.Exception(function(){
                new System.Script.Strategy.StrategyManager().Remove(new Object());
            });
            
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function CallsArrayRemoveMethod(){
            var targetStrategy=function(){
                this.IsSatisfiedBy=function(candidate){
                    return true;
                };
            }
            var targetRemove=Array.Remove;
            Array.Remove=function(){
                actual=true;
            }
            var actual=false;
            
            new System.Script.Strategy.StrategyManager().Remove(targetStrategy);
            Array.Remove=targetRemove;
            
            Assert.True(actual);
        }

        [Fact]
        function PassesStrategiesToArrayRemoveMethod(){
            var targetStrategy=function(){
                this.IsSatisfiedBy=function(candidate){
                    return true;
                };
            }
            var targetRemove=Array.Remove;
            Array.Remove=function(strategies){
                actual=strategies;
            }
            var actual=null;
            var expected=[targetStrategy,targetStrategy,targetStrategy];
            
            new System.Script.Strategy.StrategyManager(expected[0],expected[1],expected[2]).Remove(targetStrategy);
            Array.Remove=targetRemove;
            
            Assert.Equal(expected,actual);
        }
        
        [Fact]
        function PassesStrategyToArrayRemoveMethod(){
            var expected=function(){
                this.IsSatisfiedBy=function(candidate){
                    return true;
                };
            }
            var targetRemove=Array.Remove;
            Array.Remove=function(strategies,strategy){
                actual=strategy;
            }
            var actual=null;
            
            new System.Script.Strategy.StrategyManager(expected).Remove(expected);
            Array.Remove=targetRemove;
            
            Assert.Equal(expected,actual);
        }
 
        [Fact]
        function ClearsPreviouslyCachedStrategy(){
            var target=new System.Script.Strategy.StrategyManager();
            var targetStrategy=function(){
                this.IsSatisfiedBy=function(candidate){
                    return true;
                };
            }
            target.Add(targetStrategy);
            var expected="System.Script.Strategy.StrategyManager.Get: No viable strategy found.";

            target.Get(null); // cache targetStrategy
            target.Remove(targetStrategy);
            var actual=Record.Exception(function(){
                target.Get(null);
            });

            Assert.Equal(expected,actual);
        }      
    }
}